Convert .blg to CSV

# Skip conversion if CSV already exists
if (!file.exists("perfmon.csv")) {
  blg_files <- list.files(pattern = "\\.blg$", full.names = TRUE)
  blg_file  <- blg_files[which.max(file.info(blg_files)$mtime)]
  message("Starting relog conversion for: ", blg_file)
  system2(
    "relog",
    args = c(shQuote(blg_file), "-f", "CSV", "-o", "perfmon.csv"),
    stdout = "", stderr = ""
  )
  if (!file.exists("perfmon.csv")) stop("perfmon.csv not created. Check relog call.")
} else {
  message("perfmon.csv already exists; skipping conversion.")
}

Read & Tidy Performance Monitor Data

# Locate and read the CSV (skip first metadata line) using base R
perf_file <- list.files(pattern = "^perfmon\\.csv$", full.names = TRUE)[1]
perf_raw  <- read.csv(
  perf_file,
  sep          = ",",
  skip         = 0,
  header       = TRUE,
  check.names  = FALSE,
  comment.char = "",
  stringsAsFactors = FALSE
)

perf_raw <- perf_raw %>%
  filter(if_all(everything(), ~ !is.na(.) & . != ""))

# Rename first column to "Timestamp" and parse full datetime
names(perf_raw)[1] <- "Timestamp"
perf_raw <- perf_raw %>%
  mutate(
    Timestamp = mdy_hms(Timestamp)  # parse "MM/DD/YYYY HH:MM:SS.fraction"
  )

# Simplify metric names: remove any path prefix up to last slash/backslash
orig_names     <- names(perf_raw)
simple_names   <- gsub(".*\\\\", "", orig_names)  # 
names(perf_raw) <- simple_names

# Pivot to long format and convert values
perf <- perf_raw %>%
  pivot_longer(
    cols      = -Timestamp,
    names_to  = "Metric",
    values_to = "Value"
  ) %>%
  mutate(
    Value = as.numeric(Value)
  )%>%
  select(Timestamp, Metric, Value)

Read & Parse Model Run Log

model_files <- list.files(pattern = "^tm2py_run.*\\.log$", full.names = TRUE)
message("Found log files: ", paste(basename(model_files), collapse=", "))
if (length(model_files) == 0) {
  stop("No tm2py_run log files found in working directory: ", getwd())
}
model_log <- model_files[which.max(file.info(model_files)$mtime)]
message("Using log file: ", basename(model_log))

lines <- readLines(model_log)
# Define pattern: captures Date, Time, Level, and Message
datetime_str <- gsub("[()]", "", substr(lines, 1, 22))
Timestamp    <- dmy_hms(datetime_str)

# Extract Level and Message by splitting at first colon after timestamp
# Remove the timestamp and any leading spaces
rest <- substr(lines, 23, nchar(lines))
# Level is before first ':'
Level   <- str_trim(sub(":.*$", "", rest))
# Message is after the first ':'
Message <- str_trim(sub("^[^:]+:\\s*", "", rest))

# Build events tibble
model_events <- tibble(
  Timestamp = Timestamp,
  Level     = Level,
  Message   = Message
) %>%
  filter(Level %in% c("INFO", "STATUS"))

Align Data

# Filter PerfMon to last event timestamp
cutoff_ts <- max(model_events$Timestamp)
perf      <- perf %>% filter(Timestamp <= cutoff_ts)
message("PerfMon data filtered through ", cutoff_ts)

Faceted Plot of Metrics with Event Labels

Individual Interactive Charts with Significant Event Annotations